<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    *{
      margin: 0;
      padding: 0;
      list-style: none;
    }
    canvas{
      display: block;
      margin: auto;
    }
  </style>
</head>
<body>
  <canvas id="canvas"></canvas>
  <select name="level" id="select">
    <option value="0">简单</option>
    <option value="1">中等</option>
    <option value="2">困难</option>
  </select>
  <script>
    class Game {
      constructor() { 
        this.boxSize = 100;
        this.cvs = document.getElementById("canvas");
        this.ctx = this.cvs.getContext("2d");
        // 边距
        this.margin = {left:0,top:0};
        this.level = 0;
        this.levels = [3,4,5];
        this.levelArr = [];
        this.levelCopy = [];
        this.init();
      }
      init(){
        this.cvs.width = this.cvs.height = this.boxSize * this.levels[this.level];
        this.ctx.clearRect(0,0,this.cvs.width,this.cvs.height);
        this.initLevel();
        this.addClick();
      }
      // 获取边距
      getMargin() {
        const {offsetLeft,offsetTop} = this.cvs;
        this.margin = {left:offsetLeft,top:offsetTop};
      }
      drawRectLine(x,y,w,h,border) {
        this.ctx.beginPath()
        this.ctx.strokeStyle = "rgba(0,0,0,0.5)";
        this.ctx.strokeRect(x,y,w,h);
        this.ctx.closePath();
      }
      drawRect(x,y,w,h,text){
        this.ctx.beginPath();
        if(text === 'zzz'){
          this.ctx.fillStyle = "rgba(255,255,255,1)";
        }else{
          this.ctx.fillStyle = "rgba(0,0,0,0.5)";
        }
        this.ctx.fillRect(x,y,w,h);
        if(text!== 'zzz'){
          this.ctx.fillStyle = "rgba(255,255,255,1)";
          this.ctx.font = "18px Arial";
          this.ctx.fillText(text,x + w / 2,y + h / 2);
        }
        this.ctx.strokeRect(x,y,w,h);
        this.ctx.closePath();
      }
      // 打乱数组
      shuffleArray(array) {
        for (let i = array.length - 1; i > 0; i--) {
          const j = Math.floor(Math.random() * (i + 1));
          [array[i], array[j]] = [array[j], array[i]];
        }
        return array;
      }
      // 设置难度
      setLevel(level) {
        this.level = level;
        this.initLevel();
      }
      // 关卡
      initLevel() {
        this.cvs.width = this.cvs.height = this.boxSize * this.levels[this.level];
        this.getMargin();
        this.levelCopy = [];
        this.levelArr = [];
        const levelNum = this.levels[this.level];
        const count = levelNum * levelNum - 1;
        this.levelCopy = Array.from({length: count}, (_, index) => index + 1);
        this.levelCopy.push('zzz');
        this.levelArr = this.shuffleArray([...this.levelCopy]);
        this.draw();
      }
      // 绘制
      draw() {
        this.ctx.clearRect(0,0,this.cvs.width,this.cvs.height);
        this.drawRectLine(0,0,this.cvs.width,this.cvs.height);
        const levelNum = this.levels[this.level];
        const pointer = {x:0,y:0};
        for(let i = 1; i <= this.levelArr.length; i++){
          const {x,y} = pointer;
          this.drawRect(y * this.boxSize,x * this.boxSize,this.boxSize,this.boxSize,this.levelArr[i - 1]);
          pointer.y += 1;
          if(i % levelNum === 0){
            pointer.x += 1;
            pointer.y = 0;
          }
        }
      }
      // 判断是否成功
      judgeSuccess() {
        const copy = JSON.stringify(this.levelCopy);
        const arr = JSON.stringify(this.levelArr);
        if(copy === arr){
          if(this.level == 2){
            alert('你通关了真哇塞');
            return;
          }else{
            alert('恭喜你，过关了');
            this.level += 1;
            this.initLevel();
          }
        }
      }
      // 判断点击的方块是否可移动并移动
      judgeMove(obj) {
        const {x,y} = obj;
        const index = this.levels[this.level] * y + x; // 获取索引
        const zzzIndex = this.levelArr.findIndex(item => item === 'zzz');// 获取zzz索引
        const current = this.levelArr[index];
        const step = this.levels[this.level];
        if(current !== 'zzz'){
          if(index + 1 === zzzIndex || zzzIndex + 1 === index || index + step === zzzIndex || zzzIndex + step === index){
            this.levelArr[index] = 'zzz'; 
            this.levelArr[zzzIndex] = current; 
            this.draw();   
            this.judgeSuccess();
          }
        }
      }
      // 通过x,y获取方块的索引
      getIndex(x,y) {
        const obj = {x:'',y:''};
        const {left,top} = this.margin;
        const arr = Array.from({length: this.levels[this.level]},(_,index) => index * this.boxSize + this.boxSize);
        for(let i = 0; i < arr.length; i++){
          if(obj.x === '' && x < arr[i]){
            obj.x = i;
          }
          if(obj.y === '' && y < arr[i]){
            obj.y = i;
          }
        }
        return obj;
      }
      // 获取点击的方块
      getClickBox(x,y) {
        this.judgeMove(this.getIndex(x,y));
      }
      // 处理点击事件
      click = (e) => {
        const {clientX,clientY} = e;
        const x = clientX - this.margin.left;
        const y = clientY - this.margin.top;
        this.getClickBox(x,y);
      }
      // 添加点击事件
      addClick(){
        this.cvs.addEventListener("click",this.click);
      }
    }
    let game;
    game = new Game();
    const select = document.getElementById('select');
    select.onchange = function(){
      game.setLevel(this.value);
    }
  </script>
</body>
</html>